﻿//-----------------------------------------------------------------------
// <copyright file="BaseStaticAnalysisRule.cs" company="Adrien Siffermann">
//      Copyright © Adrien Siffermann 2008-2009. All rights reserved.
// </copyright>
// <summary>This file contains implementation of the BaseStaticAnalysisRule class.</summary>
// <website>http://sovfxcoprules.codeplex.com/</website>
//-----------------------------------------------------------------------

namespace SharePoint.FxCop.BestPracticesRules
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// <see cref="BaseStaticAnalysisRule"/> defines a <see cref="BaseIntrospectionRule"/>
    /// for the SharePoint.FxCop.BestPracticesRules.
    /// </summary>
    public abstract class BaseStaticAnalysisRule : BaseIntrospectionRule
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="BaseStaticAnalysisRule"/> class.
        /// </summary>
        /// <param name="name">The name of the rule.</param>
        protected BaseStaticAnalysisRule(string name) :
            base(name, "SharePoint.FxCop.BestPracticesRules.Rules", typeof(BaseStaticAnalysisRule).Assembly)
        {
            this.VarsToWatch = new List<WatchedVar>();
        }
        #endregion

        #region Protected Members
        /// <summary>
        /// Gets a list of the vars to watch for a Dispose call.
        /// </summary>
        protected List<WatchedVar> VarsToWatch { get; private set; }

        /// <summary>
        /// Gets or sets the current watched var (in a multiple IL intruction).
        /// </summary>
        protected WatchedVar CurrentVar { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the <see cref="CurrentVar"/> was load to invoke a method or property.
        /// </summary>
        protected bool IsInvoking { get; set; }

        /// <summary>
        /// Gets or sets the last property of the <see cref="CurrentVar"/> invoked.
        /// </summary>
        protected string LastProperty { get; set; }

        /// <summary>
        /// Gets or sets the last property's node of the <see cref="CurrentVar"/> invoked.
        /// </summary>
        protected Node LastPropertyNode { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether a special property of the <see cref="CurrentVar"/> was invoked.
        /// </summary>
        protected bool IsAccessingProperty { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether a special method was invoked with the <see cref="CurrentVar"/> instance.
        /// </summary>
        protected bool IsCallingSpecialMethod { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the current instructions are part of a foreach statement.
        /// </summary>
        protected bool ForEachInProgress { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether SPSite.AllWebs was invoked for the <see cref="CurrentVar"/>.
        /// </summary>
        protected bool AccessingAllWebs { get; set; }
        #endregion

        #region Static Methods
        /// <summary>
        /// Checks if the <see cref="Member"/> given is an implemented method.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>true if <paramref name="member"/> is a method, false otherwise.</returns>
        protected static bool CheckMember(Member member)
        {
            Method method = member as Method;
            if (method == null)
            {
                return false;
            }

            if (method.IsAbstract)
            {
                return false;
            }

            if (method.Instructions == null)
            {
                return false;
            }

            return true;
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Adds the problems depending on the content of <see cref="VarsToWatch"/>.
        /// </summary>
        /// <param name="member">The method member.</param>
        protected void AddProblems(Member member)
        {
            Resolution resolution = null;
            foreach (var varError in this.VarsToWatch)
            {
                if (!varError.IsDisposed)
                {
                    if (String.IsNullOrEmpty(varError.ResolutionName))
                    {
                        resolution = this.GetNamedResolution("Base", member.FullName, varError.Name);
                    }
                    else
                    {
                        resolution = this.GetNamedResolution(varError.ResolutionName, member.FullName, varError.Name);
                    }

                    if (varError.TargetedNode == null)
                    {
                        Problems.Add(new Problem(resolution));
                    }
                    else
                    {
                        Problems.Add(new Problem(resolution, varError.TargetedNode));
                    }
                }

                foreach (var propertyError in varError.WatchedProperties)
                {
                    if (String.IsNullOrEmpty(propertyError.ResolutionName))
                    {
                        resolution = this.GetNamedResolution("Base", member.FullName, varError.Name + "." + propertyError.Name);
                    }
                    else
                    {
                        resolution = this.GetNamedResolution(propertyError.ResolutionName, member.FullName, varError.Name + "." + propertyError.Name);
                    }

                    if (propertyError.TargetedNode == null)
                    {
                        Problems.Add(new Problem(resolution));
                    }
                    else
                    {
                        Problems.Add(new Problem(resolution, propertyError.TargetedNode));
                    }
                }
            }
        }

        /// <summary>
        /// Checks if the Dispose method is called.
        /// </summary>
        /// <param name="calleeName">Name of the callee.</param>
        protected void CheckDisposeCall(string calleeName)
        {
            if (calleeName == "Dispose")
            {
                if (this.CurrentVar.WatchedProperties.Count > 0)
                {
                    this.CurrentVar.IsDisposed = true;
                }
                else
                {
                    this.VarsToWatch.Remove(this.CurrentVar);
                }
            }
            else if (this.CurrentVar.TypeFullName == "Microsoft.SharePoint.Publishing.PublishingWeb" && calleeName == "Close")
            {
                this.VarsToWatch.Remove(this.CurrentVar);
            }
        }

        /// <summary>
        /// Reinits the working vars.
        /// </summary>
        protected void InitWorkingVars()
        {
            this.IsInvoking = false;
            this.IsAccessingProperty = false;
            this.IsCallingSpecialMethod = false;
            this.ForEachInProgress = false;
            this.AccessingAllWebs = false;
        }

        /// <summary>
        /// Checks the property called.
        /// </summary>
        /// <param name="calleeName">Name of the callee.</param>
        protected void CheckPropertyCalled(string calleeName)
        {
            WatchedVar watchedProperty = this.CurrentVar.WatchedProperties.FirstOrDefault(v => v.Name == this.LastProperty);
            if (calleeName == "Dispose")
            {
                if (watchedProperty != null)
                {
                    this.CurrentVar.WatchedProperties.Remove(watchedProperty);
                }
            }
            else
            {
                if (watchedProperty == null)
                {
                    this.CurrentVar.WatchedProperties.Add(new WatchedVar() { Name = this.LastProperty, TargetedNode = this.LastPropertyNode });
                }
            }
        }
        #endregion
    }
}
